#ifndef TEXTDOCUMENTLAYOUT_H
#define TEXTDOCUMENTLAYOUT_H

#include <QtGui/QTextBlockUserData>
#include <QtGui/QPlainTextDocumentLayout>
#include "ICodeEditor.h"

namespace CodeEdit {

/**
 * @struct Parenthesis
 * Data type for parenthesis matching.
 */
struct Parenthesis
{
    enum Type {
        Opened,
        Closed
    };

    inline Parenthesis() : type(Opened), pos(-1) { }
    inline Parenthesis(Type type, QChar chr, int pos)
        : type(type), chr(chr), pos(pos) { }

    Type type;
    QChar chr;
    int pos;
};

/**
 * @typedef Parentheses
 * Container of Parenthesis
 */
typedef QVector<Parenthesis> Parentheses;

/**
 * @class TextBlockUserData
 * Stores user data for text block: folding info, ifdefed out, parenthesis matching;
 * and a bunch of convenience functions.
 * Works with QTextBlock::setUserData().
 */
class TextBlockUserData : public QTextBlockUserData
{
public:
    inline TextBlockUserData()
        : _folded(false)
        , _ifdefedOut(false)
        , _foldingIndent(0)
        , _foldingStartIncluded(false)
        , _foldingEndIncluded(false) { }
    ~TextBlockUserData();

    inline TextMarks marks() const { return _marks; }
    inline void addMark(ITextMark *mark) { _marks += mark; }
    inline bool removeMark(ITextMark *mark) { return _marks.removeAll(mark); }
    inline bool hasMark(ITextMark* mark) const { return _marks.contains(mark); }
    inline void clearMarks() { _marks.clear(); }
    inline void documentClosing() {
        foreach (ITextMark *tm, _marks) {
            tm->documentClosing();
        }
        _marks.clear();
    }

    inline void setFolded(bool b) { _folded = b; }
    inline bool folded() const { return _folded; }

    inline void setParentheses(const Parentheses &parentheses) { _parentheses = parentheses; }
    inline void clearParentheses() { _parentheses.clear(); }
    inline const Parentheses &parentheses() const { return _parentheses; }
    inline bool hasParentheses() const { return !_parentheses.isEmpty(); }
    int bracesDepthDelta() const;

    inline bool setIfdefedOut() {
        bool ret = _ifdefedOut;
        _ifdefedOut = true;
        return !ret;
    }
    inline bool clearIfdefedOut() {
        bool ret = _ifdefedOut;
        _ifdefedOut = false;
        return ret;
    }
    inline bool ifdefedOut() const { return _ifdefedOut; }

    enum MatchType {
        NoMatch,
        Match,
        Mismatch
    };
    static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
    static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
    static MatchType matchCursorBackward(QTextCursor *cursor);
    static MatchType matchCursorForward(QTextCursor *cursor);
    static bool findPreviousOpenParenthesis(QTextCursor *cursor, bool select = false);
    static bool findNextClosingParenthesis(QTextCursor *cursor, bool select = false);

    static bool findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition = false);
    static bool findNextBlockClosingParenthesis(QTextCursor *cursor);

    inline int foldingIndent() const { return _foldingIndent; }
    inline void setFoldingIndent(int indent) { _foldingIndent = indent; }
    inline bool foldingStartIncluded() const { return _foldingStartIncluded; }
    inline void setFoldingStartIncluded(bool included) { _foldingStartIncluded = included; }
    inline bool foldingEndIncluded() const { return _foldingEndIncluded; }
    inline void setFoldingEndIncluded(bool included) { _foldingEndIncluded = included; }

private:
    TextMarks _marks;
    uint _folded : 1;
    uint _ifdefedOut : 1;
    uint _foldingIndent : 16;
    uint _foldingStartIncluded : 1;
    uint _foldingEndIncluded : 1;
    Parentheses _parentheses;
};

/**
 * @class CodeDocumentLayout
 * Implements a custom text layout for CodeEditor to be able to fold, highlight
 * ifdef out blocks, and match parentheses.
 * Works with QTextDocument::setDocumentLayout().
 */
class CodeDocumentLayout : public QPlainTextDocumentLayout
{
    Q_OBJECT

public:
    CodeDocumentLayout(QTextDocument *doc);
    ~CodeDocumentLayout();

    static void setParentheses(const QTextBlock &block, const Parentheses &parentheses);
    static void clearParentheses(const QTextBlock &block) { setParentheses(block, Parentheses()); }
    static Parentheses parentheses(const QTextBlock &block);
    static bool hasParentheses(const QTextBlock &block);
    static bool setIfdefedOut(const QTextBlock &block);
    static bool clearIfdefedOut(const QTextBlock &block);
    static bool ifdefedOut(const QTextBlock &block);
    static int braceDepthDelta(const QTextBlock &block);
    static int braceDepth(const QTextBlock &block);
    static void setBraceDepth(QTextBlock &block, int depth);
    static void changeBraceDepth(QTextBlock &block, int delta);
    static void setFoldingIndent(const QTextBlock &block, int indent);
    static int foldingIndent(const QTextBlock &block);
    static void changeFoldingIndent(QTextBlock &block, int delta);
    static bool canFold(const QTextBlock &block);
    static void doFoldOrUnfold(const QTextBlock &block, bool unfold);
    static bool isFolded(const QTextBlock &block);
    static void setFolded(const QTextBlock &block, bool folded);

    static TextBlockUserData *testUserData(const QTextBlock &block) {
        return static_cast<TextBlockUserData*>(block.userData());
    }
    static TextBlockUserData *userData(const QTextBlock &block) {
        TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData());
        if (!data && block.isValid())
            const_cast<QTextBlock&>(block).setUserData((data = new TextBlockUserData));
        return data;
    }

    void emitDocumentSizeChanged() { emit documentSizeChanged(documentSize()); }
    int _lastSaveRevision;
    bool _hasMarks;
};

} // namespace CodeEdit

#endif // TEXTDOCUMENTLAYOUT_H
